// src/topology/TopologyBuilder.ts

import type {
  IRuntimeRef,
  ITopologyComponentSpec,
  ITopologyPlan,
} from './TopologyTypes';

class ComponentBuilder {
  private readonly spec: ITopologyComponentSpec;
  private readonly parent: TopologyBuilder | ComponentBuilder;

  constructor(parent: TopologyBuilder | ComponentBuilder, name: string, tag: string) {
    this.parent = parent;
    this.spec = { name, tag };

    if (parent instanceof TopologyBuilder) {
      parent._addRootComponent(this.spec);
    } else {
      parent._addChild(this.spec);
    }
  }

  kind(kind: ITopologyComponentSpec['kind']): this {
    this.spec.kind = kind;
    return this;
  }

  accepts(...channels: string[]): this {
    this.spec.accepts = [...(this.spec.accepts ?? []), ...channels];
    return this;
  }

  emits(...events: string[]): this {
    this.spec.emits = [...(this.spec.emits ?? []), ...events];
    return this;
  }

  implements(ifaceName: string): this {
    this.spec.ifaceName = ifaceName;
    return this;
  }

  onRuntime(runtimeName: string): this {
    this.spec.placement ??= {};
    this.spec.placement.runtimeName = runtimeName;
    return this;
  }

  onRuntimeMount(runtimeMount: string): this {
    this.spec.placement ??= {};
    this.spec.placement.runtimeMount = runtimeMount;
    return this;
  }

  withExplicitMount(mount: string): this {
    this.spec.placement ??= {};
    this.spec.placement.explicitMount = mount;
    return this;
  }

  child(name: string, tag: string): ComponentBuilder {
    return new ComponentBuilder(this, name, tag);
  }

  end(): TopologyBuilder | ComponentBuilder {
    return this.parent;
  }

  // internal
  _getSpec(): ITopologyComponentSpec {
    return this.spec;
  }

  _addChild(child: ITopologyComponentSpec): void {
    (this.spec.children ??= []).push(child);
  }
}

export class TopologyBuilder {
  private readonly id: string;
  private _rootRuntime?: IRuntimeRef;
  private runtimes: IRuntimeRef[] = [];
  private rootComponents: ITopologyComponentSpec[] = [];

  constructor(id?: string) {
    this.id =
      id ??
      `topology-${Date.now().toString(36)}-${Math.random()
        .toString(36)
        .slice(2)}`;
  }

  rootRuntime(name: string, mount: string, tags?: string[]): this {
    const rt: IRuntimeRef = { name, mount, tags };
    this._rootRuntime = rt;
    const existing = this.runtimes.find(r => r.name === name);
    if (!existing) this.runtimes.push(rt);
    return this;
  }

  runtime(name: string, mount: string, tags?: string[]): this {
    const existing = this.runtimes.find(r => r.name === name);
    if (existing) {
      existing.mount = mount;
      existing.tags = tags;
    } else {
      this.runtimes.push({ name, mount, tags });
    }
    return this;
  }

  component(name: string, tag: string): ComponentBuilder {
    return new ComponentBuilder(this, name, tag);
  }

  build(): ITopologyPlan {
    if (!this._rootRuntime) {
      throw new Error('[TopologyBuilder] rootRuntime(...) must be specified');
    }
    return {
      id: this.id,
      rootRuntime: this._rootRuntime,
      runtimes: [...this.runtimes],
      components: this.rootComponents,
    };
  }

  // internal
  _addRootComponent(spec: ITopologyComponentSpec): void {
    this.rootComponents.push(spec);
  }
}
