// src/topology/TopologyDeployer.ts

import type {
  ITopologyPlan,
  ITopologyComponentSpec,
  IRuntimeRef,
} from './TopologyTypes';
import type {
  IRuntimeControl,
  ISpawnComponentOptions,
  ISpawnResult,
} from '../runtime/RuntimeControl';

/**
 * Naive topology deployment engine.
 *
 * Given a TopologyPlan and a set of RuntimeControl instances, it spawns
 * components on the appropriate runtimes and returns a map from
 * "tag:name" to SpawnResult.
 */
export class TopologyDeployer {
  private readonly controlsByName = new Map<string, IRuntimeControl>();
  private readonly controlsByMount = new Map<string, IRuntimeControl>();

  constructor(runtimeControls: IRuntimeControl[]) {
    for (const rc of runtimeControls) {
      this.controlsByName.set(rc.name, rc);
      this.controlsByMount.set(rc.mount, rc);
    }
  }

  async deploy(plan: ITopologyPlan): Promise<Map<string, ISpawnResult>> {
    const results = new Map<string, ISpawnResult>();

    const defaultRuntimeCtrl = this.controlsByMount.get(plan.rootRuntime.mount);
    if (!defaultRuntimeCtrl) {
      throw new Error(
        `[TopologyDeployer] No RuntimeControl registered for root runtime mount ${plan.rootRuntime.mount}`
      );
    }

    const runtimeFor = (
      placement?: ITopologyComponentSpec['placement'],
      parentRuntimeCtrl?: IRuntimeControl
    ): IRuntimeControl => {
      if (!placement) {
        // If no placement specified, use parent's runtime if available, otherwise default
        return parentRuntimeCtrl ?? defaultRuntimeCtrl;
      }

      if (placement.runtimeName) {
        const ctrl = this.controlsByName.get(placement.runtimeName);
        if (!ctrl) {
          throw new Error(
            `[TopologyDeployer] No RuntimeControl for runtimeName='${placement.runtimeName}'`
          );
        }
        return ctrl;
      }

      if (placement.runtimeMount) {
        const ctrl = this.controlsByMount.get(placement.runtimeMount);
        if (!ctrl) {
          throw new Error(
            `[TopologyDeployer] No RuntimeControl for runtimeMount='${placement.runtimeMount}'`
          );
        }
        return ctrl;
      }

      // If placement is specified but doesn't contain runtime info, use parent's runtime
      // if available, otherwise default
      return parentRuntimeCtrl ?? defaultRuntimeCtrl;
    };

    const walk = async (
      spec: ITopologyComponentSpec,
      parentRuntimeCtrl?: IRuntimeControl
    ) => {
      const ctrl = runtimeFor(spec.placement, parentRuntimeCtrl);

      const opts: ISpawnComponentOptions = {
        tag: spec.tag,
        mount: spec.placement?.explicitMount,
      };

      const spawn = await ctrl.spawnComponent(opts);
      const key = `${spec.tag}:${spec.name}`;
      results.set(key, spawn);

      for (const child of spec.children ?? []) {
        await walk(child, ctrl);
      }
    };

    for (const root of plan.components) {
      await walk(root, defaultRuntimeCtrl);
    }

    return results;
  }
}
