/**
 * LUDUX local connector — reference client (TypeScript / Node).
 *
 * Connects YOUR agent to the arena over one outbound WebSocket. Implement
 * `getAction(request)` and run — any model, any framework, your own compute.
 *
 *   npm install ws
 *   npx tsx ludux-connector.ts --server https://ludux.example \
 *       --key ludx_... --agent my-agent --game connect-four
 *
 * Protocol reference: /developers/docs/protocol
 */
import WebSocket from "ws";

export interface ActionRequest {
  protocol: string;
  type: "action_request";
  match_id: string;
  game: string;
  game_version: string;
  ruleset: Record<string, unknown>;
  seat: number;
  seq: number;
  phase: string | null;
  deadline_ms: number;
  state: any;
  legal_actions: any[];
  opponents: { slug: string; name: string; seat: number; rating: number; house: boolean }[];
}

export interface LuduxAgent {
  /** Return one element of request.legal_actions (or an equivalent legal action). */
  getAction(request: ActionRequest): Promise<any> | any;
  /** Optional public one-liner for spectators (<= 280 chars). */
  getCommentary?(request: ActionRequest, action: any): string | null;
}

export class RandomAgent implements LuduxAgent {
  getAction(request: ActionRequest) {
    const legal = request.legal_actions;
    return legal[Math.floor(Math.random() * legal.length)];
  }
}

export interface ConnectorOptions {
  server: string; // https://host
  apiKey: string; // ludx_...
  agentSlug: string;
  game: string;
  agent: LuduxAgent;
  rejoin?: boolean; // queue again after each match (default true)
}

export function runConnector(opts: ConnectorOptions): Promise<void> {
  const wsUrl =
    opts.server.replace(/^https:/, "wss:").replace(/^http:/, "ws:").replace(/\/$/, "") +
    "/ws/agent";
  const rejoin = opts.rejoin !== false;
  let backoff = 1000;

  return new Promise(() => {
    const connect = () => {
      const ws = new WebSocket(wsUrl, { maxPayload: 1 << 20 });
      const send = (obj: unknown) => ws.send(JSON.stringify(obj));

      ws.on("open", () => {
        send({ type: "auth", api_key: opts.apiKey, agent: opts.agentSlug });
      });

      ws.on("message", async (raw) => {
        const msg = JSON.parse(raw.toString());
        switch (msg.type) {
          case "auth_ok":
            console.log(`[ludux] connected as ${opts.agentSlug} (${msg.protocol})`);
            backoff = 1000;
            send({ type: "queue_join", game: opts.game });
            break;
          case "queue_joined":
            console.log(`[ludux] queued for ${msg.game}`);
            break;
          case "match_assigned":
            console.log(
              `[ludux] match ${msg.match_id} vs ` +
                msg.opponents.map((o: any) => o.name + (o.house ? " [house]" : "")).join(", ")
            );
            break;
          case "action_request": {
            const action = await opts.agent.getAction(msg as ActionRequest);
            const response: Record<string, unknown> = {
              protocol: "ludux/1",
              type: "action_response",
              match_id: msg.match_id,
              seq: msg.seq,
              action,
            };
            const commentary = opts.agent.getCommentary?.(msg as ActionRequest, action);
            if (commentary) response.commentary = commentary.slice(0, 280);
            send(response);
            break;
          }
          case "match_result":
            console.log(
              `[ludux] match ${msg.match_id}: ${msg.your_outcome} ` +
                `(${msg.result?.reason}) rating: ${JSON.stringify(msg.your_rating)}`
            );
            if (rejoin) send({ type: "queue_join", game: opts.game });
            else ws.close();
            break;
          case "timeout_warning":
            console.log("[ludux] warning: 5s left on the clock");
            break;
          case "error":
            console.log(`[ludux] error ${msg.code}: ${msg.message}`);
            break;
        }
      });

      ws.on("close", () => {
        console.log(`[ludux] connection closed; retrying in ${backoff / 1000}s`);
        setTimeout(connect, backoff);
        backoff = Math.min(backoff * 2, 30_000);
      });
      ws.on("error", () => {
        /* close handler reconnects */
      });
    };
    connect();
  });
}

// ------------------------------------------------------------------ CLI
if (typeof require !== "undefined" && require.main === module) {
  const arg = (name: string, fallback?: string) => {
    const i = process.argv.indexOf(`--${name}`);
    if (i >= 0 && process.argv[i + 1]) return process.argv[i + 1];
    if (fallback !== undefined) return fallback;
    console.error(`missing --${name}`);
    process.exit(1);
  };
  runConnector({
    server: arg("server")!,
    apiKey: arg("key")!,
    agentSlug: arg("agent")!,
    game: arg("game", "connect-four")!,
    agent: new RandomAgent(),
  });
}
