#!/usr/bin/env node
import {
  assignRoleColors,
  clearSpeakingLine,
  renderAgentMessage,
  renderAgentSpeaking,
  renderConsensus,
  renderHubBanner,
  renderMaxRounds,
  renderRoundHeader,
  renderSummary,
  renderUserInterrupt
} from "./chunk-OJL3PY36.js";
import {
  HubAgent
} from "./chunk-NZ4X6GUC.js";
import "./chunk-PDX44BCA.js";

// src/hub/hub-server.ts
import { WebSocketServer } from "ws";
import { createServer } from "http";
import chalk from "chalk";
var HubServer = class {
  constructor(config, providers, port) {
    this.config = config;
    this.providers = providers;
    this.port = port;
    this.unassignedRoles = [...config.roles];
  }
  wss = null;
  httpServer = null;
  agents = /* @__PURE__ */ new Map();
  unassignedRoles = [];
  history = [];
  aborted = false;
  currentTurnResolve = null;
  topic = "";
  /**
   * Start the hub server and run the discussion.
   */
  async start(topic) {
    this.topic = topic;
    assignRoleColors(this.config.roles);
    this.httpServer = createServer();
    this.wss = new WebSocketServer({ server: this.httpServer });
    this.wss.on("connection", (ws) => this.handleConnection(ws));
    await new Promise((resolve) => {
      this.httpServer.listen(this.port, () => resolve());
    });
    const maxRounds = this.config.maxRounds ?? 10;
    renderHubBanner(topic, this.config.roles, maxRounds, this.config.contextFiles);
    console.log(chalk.bold.cyan(`  \u{1F310} Hub server listening on ws://localhost:${this.port}`));
    console.log(chalk.dim(`  Waiting for ${this.config.roles.length} agent(s) to connect...`));
    console.log(chalk.dim(`  Each agent should run: aicli join --port ${this.port}`));
    console.log();
    await this.waitForAgents(topic);
    await this.runDiscussion(topic, maxRounds);
    this.shutdown();
  }
  abort() {
    this.aborted = true;
    if (this.currentTurnResolve) {
      this.currentTurnResolve({ content: "", passed: true });
    }
  }
  shutdown() {
    for (const agent of this.agents.values()) {
      try {
        agent.ws.close();
      } catch {
      }
    }
    this.wss?.close();
    this.httpServer?.close();
  }
  // ── Connection Handling ──────────────────────────────────────────
  handleConnection(ws) {
    ws.on("message", (raw) => {
      try {
        const msg = JSON.parse(raw.toString());
        this.handleAgentMessage(ws, msg);
      } catch {
        this.sendTo(ws, { type: "error", message: "Invalid message format" });
      }
    });
    ws.on("close", () => {
      for (const [roleId, agent] of this.agents) {
        if (agent.ws === ws) {
          console.log(chalk.yellow(`  \u26A0 Agent "${agent.roleName}" (${roleId}) disconnected`));
          this.agents.delete(roleId);
          break;
        }
      }
    });
  }
  handleAgentMessage(ws, msg) {
    switch (msg.type) {
      case "register": {
        const requestedRole = msg.roleId;
        const roleIndex = this.unassignedRoles.findIndex((r) => r.id === requestedRole);
        if (roleIndex === -1) {
          if (this.unassignedRoles.length === 0) {
            this.sendTo(ws, { type: "error", message: "All roles are already assigned." });
            ws.close();
            return;
          }
          const role = requestedRole ? void 0 : this.unassignedRoles[0];
          if (!role && requestedRole) {
            const exists = this.config.roles.some((r) => r.id === requestedRole);
            if (!exists) {
              this.sendTo(ws, { type: "error", message: `Role "${requestedRole}" not found. Available: ${this.unassignedRoles.map((r) => r.id).join(", ")}` });
              ws.close();
              return;
            }
            this.sendTo(ws, { type: "error", message: `Role "${requestedRole}" is already taken. Available: ${this.unassignedRoles.map((r) => r.id).join(", ")}` });
            ws.close();
            return;
          }
          if (role) {
            this.assignRole(ws, role);
            this.unassignedRoles.splice(0, 1);
          }
        } else {
          const role = this.unassignedRoles[roleIndex];
          this.assignRole(ws, role);
          this.unassignedRoles.splice(roleIndex, 1);
        }
        break;
      }
      case "response": {
        if (this.currentTurnResolve) {
          this.currentTurnResolve({ content: msg.content, passed: msg.passed });
          this.currentTurnResolve = null;
        }
        break;
      }
    }
  }
  assignRole(ws, role) {
    this.agents.set(role.id, {
      ws,
      roleId: role.id,
      roleName: role.name,
      ready: true
    });
    console.log(chalk.green(`  \u2713 Agent connected: ${role.name} (${role.id})`));
    this.sendTo(ws, {
      type: "welcome",
      roleId: role.id,
      roleName: role.name,
      persona: role.persona,
      topic: this.topic,
      roles: this.config.roles.map((r) => ({ id: r.id, name: r.name })),
      context: this.config.context
    });
  }
  // ── Wait for Agents ──────────────────────────────────────────────
  async waitForAgents(topic) {
    const totalRoles = this.config.roles.length;
    const deadline = Date.now() + 12e4;
    const checkInterval = 1e3;
    while (this.unassignedRoles.length > 0 && Date.now() < deadline && !this.aborted) {
      const connected = totalRoles - this.unassignedRoles.length;
      process.stdout.write(`\r  Agents: ${connected}/${totalRoles} connected. Waiting... (${Math.ceil((deadline - Date.now()) / 1e3)}s remaining)  `);
      await sleep(checkInterval);
    }
    process.stdout.write("\r\x1B[2K");
    if (this.unassignedRoles.length > 0) {
      console.log(chalk.dim(`  ${this.unassignedRoles.length} role(s) unfilled \u2014 using built-in AI agents:`));
      for (const role of this.unassignedRoles) {
        console.log(chalk.dim(`    \u2022 ${role.name} (${role.id}) \u2192 built-in`));
      }
      console.log();
    }
    const roleMap = new Map(this.config.roles.map((r) => [r.id, r]));
    for (const agent of this.agents.values()) {
      const role = roleMap.get(agent.roleId);
      this.sendTo(agent.ws, {
        type: "welcome",
        roleId: agent.roleId,
        roleName: agent.roleName,
        persona: role?.persona,
        topic,
        roles: this.config.roles.map((r) => ({ id: r.id, name: r.name })),
        context: this.config.context
      });
    }
  }
  // ── Discussion Loop ──────────────────────────────────────────────
  async runDiscussion(topic, maxRounds) {
    for (let round = 1; round <= maxRounds; round++) {
      if (this.aborted) {
        renderUserInterrupt();
        break;
      }
      renderRoundHeader(round, maxRounds);
      this.broadcastAll({ type: "round_start", round, maxRounds });
      let allPassed = true;
      for (const role of this.config.roles) {
        if (this.aborted) break;
        renderAgentSpeaking(role.name, role.id);
        let message;
        const connectedAgent = this.agents.get(role.id);
        if (connectedAgent) {
          message = await this.getRemoteResponse(connectedAgent, role, topic, round, maxRounds);
        } else {
          message = await this.getBuiltinResponse(role, topic, round, maxRounds);
        }
        clearSpeakingLine();
        this.history.push(message);
        if (!message.passed) {
          allPassed = false;
        }
        renderAgentMessage(message);
        this.broadcastAll({ type: "agent_spoke", message: serializeMessage(message) });
      }
      if (allPassed && round > 1) {
        renderConsensus();
        this.broadcastAll({ type: "discussion_end", reason: "consensus" });
        break;
      }
      if (round === maxRounds) {
        renderMaxRounds(maxRounds);
        this.broadcastAll({ type: "discussion_end", reason: "max_rounds" });
      }
    }
    await this.generateSummary(topic);
  }
  async getRemoteResponse(agent, role, _topic, round, maxRounds) {
    this.sendTo(agent.ws, {
      type: "your_turn",
      round,
      maxRounds,
      history: this.history.map(serializeMessage)
    });
    try {
      const response = await Promise.race([
        new Promise((resolve) => {
          this.currentTurnResolve = resolve;
        }),
        sleep(12e4).then(() => ({ content: `[${role.name} timed out]`, passed: true }))
      ]);
      return {
        speaker: role.id,
        speakerName: role.name,
        content: response.passed ? "" : response.content,
        timestamp: /* @__PURE__ */ new Date(),
        passed: response.passed
      };
    } catch {
      return {
        speaker: role.id,
        speakerName: role.name,
        content: "",
        timestamp: /* @__PURE__ */ new Date(),
        passed: true
      };
    }
  }
  async getBuiltinResponse(role, topic, round, maxRounds) {
    const agent = new HubAgent(role, this.providers, this.config.defaultProvider, this.config.defaultModel, this.config.context);
    return agent.speak(topic, this.history, round, maxRounds);
  }
  async generateSummary(topic) {
    if (this.history.filter((m) => !m.passed && m.speaker !== "system").length === 0) {
      return;
    }
    const summarizer = new HubAgent(
      this.config.roles[0],
      this.providers,
      this.config.defaultProvider,
      this.config.defaultModel,
      this.config.context
    );
    try {
      console.log(chalk.dim("  Generating summary..."));
      const summary = await summarizer.summarize(topic, this.history);
      renderSummary(summary);
      this.broadcastAll({ type: "discussion_end", reason: "max_rounds", summary });
    } catch (err) {
      console.log(chalk.red(`  Summary generation failed: ${err.message}`));
    }
  }
  // ── Messaging ────────────────────────────────────────────────────
  sendTo(ws, msg) {
    if (ws.readyState === ws.OPEN) {
      ws.send(JSON.stringify(msg));
    }
  }
  broadcastAll(msg) {
    const data = JSON.stringify(msg);
    for (const agent of this.agents.values()) {
      if (agent.ws.readyState === agent.ws.OPEN) {
        agent.ws.send(data);
      }
    }
  }
};
function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function serializeMessage(msg) {
  return {
    ...msg,
    timestamp: msg.timestamp instanceof Date ? msg.timestamp.toISOString() : msg.timestamp
  };
}
export {
  HubServer
};
