// src/flow/ResultsPanel.ts
//
// ResultsPanel - A MatrixActor that hosts query results as child components.
//
// Per the vision: "ALL results are components. ALL components can be results."
//
// Every query result becomes an addressable child component:
//   flowpad.results.result-0 → GridComponent (SQL query)
//   flowpad.results.result-1 → TreeShell (clone of remote component)
//   flowpad.results.result-2 → JsonViewerComponent (introspect data)
//   flowpad.results.result-3 → ErrorComponent (error with stack)
//   flowpad.results.result-4 → CommandResultComponent (command response)
//
// Results can be:
//   - Clone mode: Full replica with state replication (addReplicaBind)
//   - Proxy mode: Local shell that forwards to remote
//   - Data mode: Viewer component for data (JSON, grid, error, etc.)

import { MatrixActor } from '../core/MatrixActor';
import { ShellRegistry } from './ShellRegistry';
import type { IMatrixContext } from '../engine/core/IMatrixContext';
import type { IMatrixComponent } from './IMatrixComponent';
import { createComponent } from './MatrixComponentImpl';
import { RequestReply } from '../engine/remoting/RequestReply';
import { TopicRouter } from '../engine/messaging/TopicRouter';

// Type guard removed - we now use transport-based introspect instead of direct method calls

/**
 * Result type determines which viewer component renders the result.
 */
export type ResultType =
  | 'introspect'   // Component introspect data → JsonViewer or live component
  | 'data'         // Generic data → JsonViewer
  | 'command'      // Command result → CommandResultViewer
  | 'error'        // Error → ErrorViewer
  | 'clone'        // Clone of remote component → Same shell as remote
  | 'proxy'        // Proxy to remote component → Same shell as remote
  | 'grid'         // Tabular data → GridViewer
  | 'custom';      // Custom component class provided

/**
 * Descriptor for adding a result to the panel.
 */
export interface IResultDescriptor {
  /** Result type determines viewer component */
  type: ResultType;

  /** The result data/payload */
  data: unknown;

  /** For clone/proxy mode: remote mount path */
  remoteMount?: string;

  /** For custom type: explicit component class */
  componentClass?: new () => MatrixActor;

  /** Optional props to pass to the viewer component */
  props?: Record<string, unknown>;

  /** Optional label for the result */
  label?: string;
}

/**
 * Information about a rendered result.
 */
export interface IResultInfo {
  id: string;
  mount: string;
  type: ResultType;
  label?: string;
  remoteMount?: string;
}

/**
 * ResultsPanel - MatrixActor that manages result child components.
 *
 * Mount example: flowpad.results
 *
 * Accepts:
 *   - addResult: { descriptor: ResultDescriptor } → { id, mount }
 *   - removeResult: { id: string }
 *   - clearResults: {}
 *   - getResults: {} → ResultInfo[]
 *
 * Emits:
 *   - resultAdded: { id, mount, type, label }
 *   - resultRemoved: { id, mount }
 *   - resultsCleared: { count }
 *
 * Children are addressable at:
 *   flowpad.results.result-0
 *   flowpad.results.result-1
 *   etc.
 */
export class ResultsPanel extends MatrixActor {
  static accepts = {
    addResult: { descriptor: 'ResultDescriptor' },
    removeResult: { id: 'string' },
    clearResults: {},
    getResults: {},
  };

  static emits = {
    resultAdded: { id: 'string', mount: 'string', type: 'string', label: 'string?' },
    resultRemoved: { id: 'string', mount: 'string' },
    resultsCleared: { count: 'number' },
  };

  private _resultCounter = 0;
  private _resultInfos = new Map<string, IResultInfo>();
  private _resultData = new Map<string, unknown>();

  /**
   * Add a result to the panel.
   * Returns the result's id and mount path.
   */
  async onAddResult(params: {
    descriptor: IResultDescriptor;
  }): Promise<{ id: string; mount: string; component?: IMatrixComponent }> {
    const { descriptor } = params;
    const id = `result-${this._resultCounter++}`;

    // Determine which component class to use based on result type
    const ComponentClass = this._resolveComponentClass(descriptor);

    if (!ComponentClass) {
      // If no component class, create a fallback JsonViewer
      // eslint-disable-next-line no-console -- flow infrastructure, no actor context
      console.warn(`[ResultsPanel] No component class for type '${descriptor.type}', using fallback`);
      return this._addFallbackResult(id, descriptor);
    }

    // Prepare props for the viewer component
    const props = {
      ...descriptor.props,
      resultData: descriptor.data,
      resultType: descriptor.type,
      resultLabel: descriptor.label,
    };

    // Add as child using MatrixActor.addChild()
    const mount = await this.addChild(id, ComponentClass, { props });

    // For clone mode: add replica binding to remote mount via transport
    // No cheating! Use transport instead of direct method call
    if (descriptor.type === 'clone' && descriptor.remoteMount && this._context) {
      const inboxTopic = TopicRouter.inbox(mount);
      this._context.transport.publish(inboxTopic, {
        op: 'addReplicaBind',
        payload: { mount: descriptor.remoteMount },
      });
      // eslint-disable-next-line no-console -- flow infrastructure, no actor context
      console.log(`[ResultsPanel] Added replica bind for ${id} → ${descriptor.remoteMount}`);
    }

    // Track result info
    const resultInfo: IResultInfo = {
      id,
      mount,
      type: descriptor.type,
      label: descriptor.label,
      remoteMount: descriptor.remoteMount,
    };
    this._resultInfos.set(id, resultInfo);

    // Create IMatrixComponent wrapper for the result via transport introspect
    // No cheating! Use RequestReply instead of direct method call
    let component: IMatrixComponent | undefined;
    if (this._context) {
      try {
        const introspect = await RequestReply.execute(
          this._context,
          mount,
          'introspect',
          {},
          { timeoutMs: 5000 }
        );
        component = createComponent(this._context, { ...(introspect as Record<string, unknown>), mount });
      } catch {
        // Fallback if introspect fails
        component = createComponent(this._context, { mount, componentClass: ComponentClass.name });
      }
    }

    // Emit event
    this.emit('resultAdded', resultInfo);

    return { id, mount, component };
  }

  /**
   * Remove a result by id.
   */
  async onRemoveResult(params: { id: string }): Promise<void> {
    const { id } = params;
    const info = this._resultInfos.get(id);

    if (!info) {
      // eslint-disable-next-line no-console -- flow infrastructure, no actor context
      console.warn(`[ResultsPanel] Result '${id}' not found`);
      return;
    }

    // Remove child
    await this.removeChild(id);

    // Clean up tracking
    this._resultInfos.delete(id);

    // Emit event
    this.emit('resultRemoved', { id, mount: info.mount });
  }

  /**
   * Clear all results.
   */
  async onClearResults(): Promise<void> {
    const count = this._resultInfos.size;

    // Remove all children
    for (const id of this.childIds()) {
      await this.removeChild(id);
    }

    // Clear tracking
    this._resultInfos.clear();
    this._resultCounter = 0;

    // Emit event
    this.emit('resultsCleared', { count });
  }

  /**
   * Get information about all results.
   */
  onGetResults(): IResultInfo[] {
    return Array.from(this._resultInfos.values());
  }

  /**
   * Get result info by id.
   */
  getResultInfo(id: string): IResultInfo | undefined {
    return this._resultInfos.get(id);
  }

  /**
   * Get the number of results.
   */
  get resultCount(): number {
    return this._resultInfos.size;
  }

  /**
   * Resolve which component class to use for a result type.
   * Uses ShellRegistry for type→shell mapping.
   */
  private _resolveComponentClass(descriptor: IResultDescriptor): (new () => MatrixActor) | undefined {
    // Custom type: use provided class
    if (descriptor.type === 'custom' && descriptor.componentClass) {
      return descriptor.componentClass;
    }

    // Introspect result with shellTag: use the component's own shell
    if (descriptor.type === 'introspect' || descriptor.type === 'clone' || descriptor.type === 'proxy') {
      const data = descriptor.data as Record<string, unknown> | undefined;
      if (data?.componentClass && typeof data.componentClass === 'string') {
        // Try to get shell info from registry
        const shellInfo = ShellRegistry.lookup(data.componentClass);
        if (shellInfo) {
          // For now, we can't directly get the component class from shellTag
          // This would require a more sophisticated registry
          // Fall through to default viewers
        }
      }
    }

    // Map result types to default viewer components
    // These viewer components should be registered in ShellRegistry
    switch (descriptor.type) {
      case 'introspect':
      case 'data':
        return ShellRegistry.lookup('JsonViewer')
          ? undefined // Would need component class from registry
          : undefined;

      case 'error':
        return ShellRegistry.lookup('ErrorViewer')
          ? undefined
          : undefined;

      case 'command':
        return ShellRegistry.lookup('CommandResultViewer')
          ? undefined
          : undefined;

      case 'grid':
        return ShellRegistry.lookup('GridViewer')
          ? undefined
          : undefined;

      case 'clone':
      case 'proxy':
        // For clone/proxy, we need the remote component's class
        // This requires introspect data to include componentClass
        return undefined;

      default:
        return undefined;
    }
  }

  /**
   * Add a fallback result when no component class is available.
   * Creates a simple data holder component.
   */
  private async _addFallbackResult(
    id: string,
    descriptor: IResultDescriptor
  ): Promise<{ id: string; mount: string }> {
    // For now, just track the result without creating a child component
    // The shell layer will handle rendering the raw data
    const mount = `${this._context?.mount}.${id}`;

    const resultInfo: IResultInfo = {
      id,
      mount,
      type: descriptor.type,
      label: descriptor.label,
      remoteMount: descriptor.remoteMount,
    };
    this._resultInfos.set(id, resultInfo);

    // Store the data for shell access
    this._resultData.set(id, descriptor.data);

    this.emit('resultAdded', resultInfo);

    return { id, mount };
  }

  /**
   * Get raw result data by id (for shell rendering).
   */
  getResultData(id: string): unknown {
    return this._resultData.get(id);
  }
}

/**
 * ResultsPanelShell would go in a separate file in the demo.
 * It renders the ResultsPanel's children into a container.
 */
