/**
 * AgentHostRegistry — multi-host registration (WP-03).
 * Optional descriptor persistence under switchboard/hosts/; no Active wiring.
 */

import type {
  AgentHostAdapter,
  AgentHostRegistryContract,
} from "../contracts/AgentHostAdapter.ts";
import type { HostCapabilityLevel } from "../contracts/HostCapabilities.ts";
import type { HostDescriptor } from "../contracts/HostDescriptor.ts";
import { AtomicFile } from "../atomic/AtomicFile.ts";
import { SwitchboardPaths } from "../paths/SwitchboardPaths.ts";
import { capabilityLevelMeets } from "./capabilityPresets.ts";

export class AgentHostRegistryError extends Error {
  constructor(message: string) {
    super(message);
    this.name = "AgentHostRegistryError";
  }
}

export interface AgentHostRegistryOptions {
  /** When set, register/unregister mirrors HostDescriptor JSON under hosts/. */
  paths?: SwitchboardPaths;
}

export class AgentHostRegistry implements AgentHostRegistryContract {
  readonly #hosts = new Map<string, AgentHostAdapter>();
  readonly #paths: SwitchboardPaths | undefined;
  readonly #persistJobs = new Map<string, Promise<void>>();

  constructor(opts: AgentHostRegistryOptions = {}) {
    this.#paths = opts.paths;
  }

  register(adapter: AgentHostAdapter): void {
    const id = String(adapter.hostId ?? "").trim();
    if (!id) throw new AgentHostRegistryError("adapter.hostId is required");
    this.#hosts.set(id, adapter);
    if (this.#paths) {
      this.#persistJobs.set(id, this.#persistDescriptor(adapter));
    }
  }

  /** Await outstanding descriptor persistence (tests / diagnostics). */
  async flush(): Promise<void> {
    const pending = [...this.#persistJobs.values()];
    this.#persistJobs.clear();
    if (pending.length) await Promise.all(pending);
  }

  unregister(hostId: string): void {
    const id = String(hostId ?? "").trim();
    this.#hosts.delete(id);
    this.#persistJobs.delete(id);
    if (this.#paths) {
      const file = this.#paths.hostFile(id);
      void AtomicFile.exists(file).then((ok) => {
        if (ok) {
          return import("node:fs/promises").then((fs) =>
            fs.unlink(file).catch(() => undefined),
          );
        }
        return undefined;
      });
    }
  }

  get(hostId: string): AgentHostAdapter {
    const adapter = this.#hosts.get(String(hostId ?? "").trim());
    if (!adapter) {
      throw new AgentHostRegistryError(`host not registered: ${hostId}`);
    }
    return adapter;
  }

  has(hostId: string): boolean {
    return this.#hosts.has(String(hostId ?? "").trim());
  }

  list(): AgentHostAdapter[] {
    return [...this.#hosts.values()];
  }

  /** Enabled hosts only (descriptor.enabled !== false). */
  async listEnabled(): Promise<AgentHostAdapter[]> {
    const out: AgentHostAdapter[] = [];
    for (const adapter of this.#hosts.values()) {
      const d = await adapter.describe();
      if (d.enabled) out.push(adapter);
    }
    return out;
  }

  async describeAll(): Promise<HostDescriptor[]> {
    const descriptors: HostDescriptor[] = [];
    for (const adapter of this.#hosts.values()) {
      descriptors.push(await adapter.describe());
    }
    return descriptors.sort((a, b) => a.hostId.localeCompare(b.hostId));
  }

  async assertConformance(
    hostId: string,
    minimumLevel: HostCapabilityLevel,
  ): Promise<HostDescriptor> {
    const adapter = this.get(hostId);
    const descriptor = await adapter.describe();
    if (!capabilityLevelMeets(descriptor.capabilities.level, minimumLevel)) {
      throw new AgentHostRegistryError(
        `host ${hostId} capability ${descriptor.capabilities.level} below required ${minimumLevel}`,
      );
    }
    return descriptor;
  }

  async #persistDescriptor(adapter: AgentHostAdapter): Promise<void> {
    if (!this.#paths) return;
    try {
      const descriptor = await adapter.describe();
      await AtomicFile.writeJson(this.#paths.hostFile(adapter.hostId), descriptor);
    } catch {
      /* persistence is best-effort; in-memory registry remains source for WP-03 */
    }
  }
}
