// src/flow/MatrixComponentImpl.ts
//
// Implementation of IMatrixComponent - the fluent interface for Matrix components.
// Every method that navigates to another component returns IMatrixComponent,
// enabling LINQ-style chaining.

import type { IMatrixContext } from '../engine/core/IMatrixContext';
import { RequestReply } from '../engine/remoting/RequestReply';
import { TopicRouter } from '../engine/messaging/TopicRouter';
import type {
  IMatrixComponent,
  IComponentMetadata,
  IMatrixComponentOptions,
} from './IMatrixComponent';

/**
 * Parameters for introspect requests.
 */
export interface IIntrospectRequest {
  depth?: number;
  includePrivate?: boolean;
}

/**
 * Type for objects that can be mounted with a context.
 */
interface IMountable {
  mount(context: IMatrixContext): Promise<void> | void;
}

/**
 * Type guard to check if an object is mountable.
 */
function isMountable(obj: unknown): obj is IMountable {
  return typeof (obj as IMountable).mount === 'function';
}

/**
 * MatrixComponentImpl - Implementation of IMatrixComponent interface.
 *
 * This is the concrete class that implements the fluent API.
 * All navigation methods return IMatrixComponent for chaining.
 */
export class MatrixComponentImpl implements IMatrixComponent {
  private _context: IMatrixContext;
  private _metadata: IComponentMetadata;

  constructor(options: IMatrixComponentOptions) {
    this._context = options.context;
    this._metadata = options.metadata;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Static Factory Methods
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Create an IMatrixComponent by calling introspect on a remote mount.
   * This is the primary way to get an IMatrixComponent.
   */
  static async fromMount(
    context: IMatrixContext,
    mount: string
  ): Promise<IMatrixComponent> {
    const result = await RequestReply.execute<Record<string, never>, IComponentMetadata>(
      context,
      mount,
      'introspect',
      {},
      { timeoutMs: 5000 }
    );

    return new MatrixComponentImpl({
      context,
      metadata: { ...result, mount },
    });
  }

  /**
   * Create an IMatrixComponent from existing metadata.
   * Useful when you already have introspect data.
   */
  static fromMetadata(
    context: IMatrixContext,
    metadata: IComponentMetadata
  ): IMatrixComponent {
    return new MatrixComponentImpl({ context, metadata });
  }

  /**
   * Create an IMatrixComponent from raw introspect result.
   * Wraps any object that has mount/componentClass/accepts.
   */
  static fromIntrospect(
    context: IMatrixContext,
    introspect: Record<string, unknown>
  ): IMatrixComponent {
    const metadata: IComponentMetadata = {
      mount: typeof introspect.mount === 'string' ? introspect.mount : '',
      componentClass: typeof introspect.componentClass === 'string' ? introspect.componentClass : 'Unknown',
      accepts: Array.isArray(introspect.accepts) ? introspect.accepts as string[] : [],
      emits: Array.isArray(introspect.emits) ? introspect.emits as string[] : [],
      shellTag: typeof introspect.shellTag === 'string' ? introspect.shellTag : undefined,
      moduleUrl: typeof introspect.moduleUrl === 'string' ? introspect.moduleUrl : undefined,
      acceptsSchema: introspect.acceptsSchema as Record<string, unknown> | undefined,
      emitsSchema: introspect.emitsSchema as Record<string, unknown> | undefined,
    };
    return new MatrixComponentImpl({ context, metadata });
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Metadata Access (IMatrixComponent)
  // ═══════════════════════════════════════════════════════════════════════════

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

  get componentClass(): string {
    return this._metadata.componentClass;
  }

  get accepts(): string[] {
    return this._metadata.accepts ?? [];
  }

  get emits(): string[] {
    return this._metadata.emits ?? [];
  }

  get shellTag(): string | undefined {
    return this._metadata.shellTag;
  }

  get metadata(): IComponentMetadata {
    return this._metadata;
  }

  get canRender(): boolean {
    return !!this._metadata.shellTag;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Navigation - Returns IMatrixComponent for chaining
  // ═══════════════════════════════════════════════════════════════════════════

  /**
   * Get a child component by name.
   * Calls introspect on the child mount and returns IMatrixComponent.
   */
  async child(name: string): Promise<IMatrixComponent> {
    // Construct child mount path
    const childMount = `${this._metadata.mount}.${name}`;

    // Call introspect on the child
    const result = await RequestReply.execute<IIntrospectRequest, IComponentMetadata>(
      this._context,
      childMount,
      'introspect',
      {},
      { timeoutMs: 5000 }
    );

    return new MatrixComponentImpl({
      context: this._context,
      metadata: { ...result, mount: childMount },
    });
  }

  /**
   * Get the parent component.
   * Derives parent mount from current mount and calls introspect.
   */
  async parent(): Promise<IMatrixComponent | null> {
    const parts = this._metadata.mount.split('.');
    if (parts.length <= 1) {
      return null; // Already at root
    }

    const parentMount = parts.slice(0, -1).join('.');

    try {
      const result = await RequestReply.execute<IIntrospectRequest, IComponentMetadata>(
        this._context,
        parentMount,
        'introspect',
        {},
        { timeoutMs: 5000 }
      );

      return new MatrixComponentImpl({
        context: this._context,
        metadata: { ...result, mount: parentMount },
      });
    } catch {
      return null;
    }
  }

  /**
   * Refresh metadata by calling introspect again.
   */
  async refresh(): Promise<IMatrixComponent> {
    const result = await RequestReply.execute<IIntrospectRequest, IComponentMetadata>(
      this._context,
      this._metadata.mount,
      'introspect',
      {},
      { timeoutMs: 5000 }
    );

    this._metadata = { ...result, mount: this._metadata.mount };
    return this;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Commands
  // ═══════════════════════════════════════════════════════════════════════════

  async send<TParams = unknown, TResult = unknown>(
    command: string,
    params?: TParams
  ): Promise<TResult> {
    return RequestReply.execute<TParams | undefined, TResult>(
      this._context,
      this._metadata.mount,
      command,
      params,
      { timeoutMs: 10000 }
    );
  }

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

  subscribe(
    event: string,
    handler: (payload: unknown, meta: { topic: string; ts: number }) => void
  ): () => void {
    const eventsTopic = TopicRouter.events(this._metadata.mount);
    return this._context.transport.subscribe(eventsTopic, (p: unknown, meta: { topic: string; ts: number }) => {
      const envelope = p as { op?: string; payload?: unknown };
      if (envelope?.op === event) {
        handler(envelope.payload, meta);
      }
    });
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Rendering
  // ═══════════════════════════════════════════════════════════════════════════

  async render(container: HTMLElement): Promise<HTMLElement | null> {
    const shellTag = this._metadata.shellTag;
    const moduleUrl = this._metadata.moduleUrl;

    if (!shellTag) {
      // eslint-disable-next-line no-console -- flow infrastructure, no actor context
      console.warn(
        `[MatrixComponent] Cannot render ${this._metadata.componentClass}: no shellTag`
      );
      return null;
    }

    // Load the module if needed
    if (moduleUrl && !customElements.get(shellTag)) {
      try {
        await import(/* @vite-ignore */ moduleUrl);
        await customElements.whenDefined(shellTag);
      } catch (err) {
        // eslint-disable-next-line no-console -- flow infrastructure, no actor context
        console.error(
          `[MatrixComponent] Failed to load module for ${shellTag}:`,
          err
        );
        return null;
      }
    }

    if (!customElements.get(shellTag)) {
      // eslint-disable-next-line no-console -- flow infrastructure, no actor context
      console.warn(
        `[MatrixComponent] Cannot render ${shellTag}: custom element not defined`
      );
      return null;
    }

    // Create and configure the shell element
    const ghost = document.createElement(shellTag);
    ghost.setAttribute('data-remote-mount', this._metadata.mount);
    container.appendChild(ghost);

    // Mount with derived context
    const ghostContext = this._context.derive(`ghost-${Date.now()}`);
    if (isMountable(ghost)) {
      await ghost.mount(ghostContext);
    }

    return ghost;
  }

  /**
   * Render a clone that syncs with the original component.
   *
   * ═══════════════════════════════════════════════════════════════════════════
   * ✅ UI CONTROL INVERSION: Uses transport for replica binding
   * ═══════════════════════════════════════════════════════════════════════════
   */
  async renderClone(container: HTMLElement): Promise<HTMLElement | null> {
    const ghost = await this.render(container);
    if (!ghost) return null;

    // ═══════════════════════════════════════════════════════════════════════
    // ✅ NO GHOST CASTING: Use transport to send addReplicaBind command
    // Instead of accessing ghost._component, we send via transport.
    // ═══════════════════════════════════════════════════════════════════════
    const ghostMount = ghost.getAttribute('data-mount');
    if (ghostMount) {
      try {
        const inboxTopic = TopicRouter.inbox(ghostMount);
        this._context.transport.publish(inboxTopic, {
          op: 'addReplicaBind',
          payload: { mount: this._metadata.mount },
        });
      } catch (err) {
        // eslint-disable-next-line no-console -- flow infrastructure, no actor context
        console.error(`[MatrixComponent] Failed to add replica bind:`, err);
      }
    }

    return ghost;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Dynamic Method Access
  // ═══════════════════════════════════════════════════════════════════════════

  asProxy(): this & Record<string, (...args: unknown[]) => Promise<unknown>> {
    const self = this as Record<string | symbol, unknown>;
    return new Proxy(this, {
      get: (target, key: string | symbol) => {
        if (typeof key === 'symbol') return self[key];
        if (key in target) return self[key];

        // Check if it's an accepts command
        const accepts = target._metadata.accepts ?? [];
        if (accepts.includes(key)) {
          return async (...args: unknown[]) => {
            const payload =
              args.length === 1
                ? args[0]
                : args.length === 0
                  ? undefined
                  : args;
            return target.send(key, payload);
          };
        }

        return undefined;
      },
    }) as this & Record<string, (...args: unknown[]) => Promise<unknown>>;
  }

  // ═══════════════════════════════════════════════════════════════════════════
  // Conversion
  // ═══════════════════════════════════════════════════════════════════════════

  toJSON(): IComponentMetadata {
    return this._metadata;
  }

  toString(): string {
    return `IMatrixComponent<${this._metadata.componentClass}@${this._metadata.mount}>`;
  }
}

/**
 * Factory function to create an IMatrixComponent from introspect result.
 * This is the main entry point for wrapping flow results.
 */
export function createComponent(
  context: IMatrixContext,
  introspect: unknown
): IMatrixComponent {
  const data = (introspect && typeof introspect === 'object')
    ? introspect as Record<string, unknown>
    : {};
  return MatrixComponentImpl.fromIntrospect(context, data);
}

/**
 * Factory function to get an IMatrixComponent from a mount path.
 */
export async function getComponent(
  context: IMatrixContext,
  mount: string
): Promise<IMatrixComponent> {
  return MatrixComponentImpl.fromMount(context, mount);
}
