// src/lisp/vlm/VLMRouter.ts
// VLM Router - Node discovery and load balancing
//
// Manages the pool of bootstrap nodes available for distributed evaluation.
// Uses round-robin selection with health monitoring.
//
// Reference: Vision/18-VIRTUAL-LISP-MACHINE.md

import { MatrixActor } from '../../core/MatrixActor';
import type { INodeInfo } from './INodeInfo';

/**
 * Derive sibling VLM component mounts from own mount.
 * E.g., if at "flowpad.vlm-router", siblings are:
 *   - router: flowpad.vlm-router
 *   - join: flowpad.vlm-join
 *   - atomizer: flowpad.vlm-atomizer
 *   - eval: flowpad.vlm-eval
 *   - nodePrefix: flowpad.vlm-node-
 */
export function getVLMNamespace(mount: string): {
  router: string;
  join: string;
  atomizer: string;
  eval: string;
  nodePrefix: string;
} {
  const parts = mount.split('.');
  const lastPart = parts[parts.length - 1];

  let parentMount: string;
  // Helper: join parent + name, avoiding leading dot when parent is empty (root mount)
  const join = (parent: string, name: string) => parent ? `${parent}.${name}` : name;

  if (lastPart.startsWith('vlm-')) {
    // FlowPad style: flowpad.vlm-router (or root-level vlm-router)
    parentMount = parts.slice(0, -1).join('.');
    return {
      router: join(parentMount, 'vlm-router'),
      join: join(parentMount, 'vlm-join'),
      atomizer: join(parentMount, 'vlm-atomizer'),
      eval: join(parentMount, 'vlm-eval'),
      nodePrefix: join(parentMount, 'vlm-node-'),
    };
  } else if (['router', 'join', 'atomizer'].includes(lastPart) || lastPart.match(/^node\./)) {
    // Test style: vlm.router
    parentMount = parts.slice(0, -1).join('.');
    return {
      router: join(parentMount, 'router'),
      join: join(parentMount, 'join'),
      atomizer: join(parentMount, 'atomizer'),
      eval: join(parentMount, 'eval'),
      nodePrefix: join(parentMount, 'node.'),
    };
  }
  // Default: assume test style
  parentMount = mount.replace(/\.(router|join|atomizer|eval|node\.\d+)$/, '');
  return {
    router: join(parentMount, 'router'),
    join: join(parentMount, 'join'),
    atomizer: join(parentMount, 'atomizer'),
    eval: join(parentMount, 'eval'),
    nodePrefix: join(parentMount, 'node.'),
  };
}

/**
 * VLMRouter - Manages bootstrap node pool
 *
 * Responsibilities:
 * - Node registration and health monitoring
 * - Round-robin load balancing
 * - Heartbeat tracking
 *
 * Mount: {namespace}.vlm-router
 */
export class VLMRouter extends MatrixActor {
  static accepts = {
    register: { nodeId: 'string', mount: 'string' },
    unregister: { nodeId: 'string' },
    getNode: {},
    getNodes: { count: 'number' },
    heartbeat: { nodeId: 'string', load: 'number?' },
    listNodes: {},
  };

  static emits = {
    nodeRegistered: { nodeId: 'string', mount: 'string', totalNodes: 'number' },
    nodeUnregistered: { nodeId: 'string' },
    noNodesAvailable: {},
  };

  private _nodes: Map<string, INodeInfo> = new Map();
  private _roundRobin: number = 0;
  protected _ns!: ReturnType<typeof getVLMNamespace>;

  protected async onBootstrap(): Promise<void> {
    this._ns = getVLMNamespace(this._context!.mount);
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log('[VLMRouter] Initialized at', this._context!.mount);
  }

  async onRegister(params: { nodeId: string; mount: string }): Promise<{ success: boolean }> {
    const info: INodeInfo = {
      nodeId: params.nodeId,
      mount: params.mount,
      registeredAt: Date.now(),
      lastHeartbeat: Date.now(),
      load: 0,
      healthy: true,
    };
    this._nodes.set(params.nodeId, info);
    // eslint-disable-next-line no-console -- standalone lisp runtime, no actor context
    console.log(`[VLMRouter] Node registered: ${params.nodeId} at ${params.mount} (total: ${this._nodes.size})`);
    this.emit('nodeRegistered', { nodeId: params.nodeId, mount: params.mount, totalNodes: this._nodes.size });
    return { success: true };
  }

  async onUnregister(params: { nodeId: string }): Promise<void> {
    this._nodes.delete(params.nodeId);
    this.emit('nodeUnregistered', { nodeId: params.nodeId });
  }

  async onHeartbeat(params: { nodeId: string; load?: number }): Promise<void> {
    const node = this._nodes.get(params.nodeId);
    if (node) {
      node.lastHeartbeat = Date.now();
      node.load = params.load ?? 0;
      node.healthy = true;
    }
  }

  async onGetNode(): Promise<{ mount: string } | null> {
    const available = this._getHealthyNodes();
    if (available.length === 0) {
      this.emit('noNodesAvailable', {});
      return null;
    }
    const node = available[this._roundRobin % available.length];
    this._roundRobin++;
    return { mount: node.mount };
  }

  async onGetNodes(params: { count: number }): Promise<{ mounts: string[] }> {
    const available = this._getHealthyNodes();
    const mounts: string[] = [];
    for (let i = 0; i < params.count; i++) {
      if (available.length === 0) break;
      mounts.push(available[(this._roundRobin + i) % available.length].mount);
    }
    this._roundRobin += params.count;
    return { mounts };
  }

  async onListNodes(): Promise<{ nodes: INodeInfo[] }> {
    return { nodes: [...this._nodes.values()] };
  }

  private _getHealthyNodes(): INodeInfo[] {
    const now = Date.now();
    return [...this._nodes.values()].filter(n => {
      // Consider unhealthy if no heartbeat in 90 seconds (3x heartbeat interval)
      if (now - n.lastHeartbeat > 90000) {
        n.healthy = false;
      }
      return n.healthy;
    });
  }
}
