/**
 * High-level presence agent (RFC-001 §4.1 steps 10–11, §4.5).
 *
 * Ties the pieces together for one agent: join the fleet presence room, set
 * native Matrix presence online (for Element's dot), and run the authoritative
 * `mosaic.presence` heartbeat loop. This is the P1 slice of what a harness does
 * on spin — no enrollment/token-minting/introductions (those are P2).
 */

import {
  HeartbeatEmitter,
  startHeartbeatLoop,
  type HeartbeatAgentIdentity,
  type HeartbeatLoopHandle,
} from './heartbeat.js';
import type { MinimalMatrixClient } from './matrix-client.js';
import { DEFAULT_LIVENESS_POLICY, type LivenessPolicy, type PresenceStatus } from './types.js';

export interface PresenceAgentOptions {
  client: MinimalMatrixClient;
  agent: HeartbeatAgentIdentity;
  /** Fleet presence room id (or alias) to heartbeat into. */
  roomId: string;
  /** Heartbeat cadence; defaults to the policy interval. */
  intervalMs?: number;
  policy?: LivenessPolicy;
  missionId?: string;
  onError?: (err: unknown) => void;
}

export class PresenceAgent {
  private readonly intervalMs: number;
  private readonly emitter: HeartbeatEmitter;
  private loop: HeartbeatLoopHandle | undefined;
  private resolvedRoomId: string | undefined;

  constructor(private readonly opts: PresenceAgentOptions) {
    const policy = opts.policy ?? DEFAULT_LIVENESS_POLICY;
    this.intervalMs = opts.intervalMs ?? policy.heartbeatIntervalMs;
    this.emitter = new HeartbeatEmitter({
      agent: opts.agent,
      intervalMs: this.intervalMs,
      missionId: opts.missionId,
    });
  }

  /** Join the fleet room and go present. Returns the resolved room id. */
  async connect(): Promise<string> {
    this.resolvedRoomId = await this.opts.client.joinRoom(this.opts.roomId);
    await this.opts.client.setPresence(this.opts.agent.mxid, 'online', 'mosaic.presence heartbeat');
    return this.resolvedRoomId;
  }

  /** Start the heartbeat loop (emits immediately, then every intervalMs). */
  start(status: () => PresenceStatus = () => 'online'): void {
    const roomId = this.resolvedRoomId ?? this.opts.roomId;
    this.loop = startHeartbeatLoop({
      emitter: this.emitter,
      intervalMs: this.intervalMs,
      status,
      onError: this.opts.onError,
      send: async (content) => {
        await this.opts.client.sendHeartbeat(roomId, content);
      },
    });
  }

  get currentSeq(): number {
    return this.emitter.currentSeq;
  }

  /**
   * Stop only the heartbeat loop, sending NO graceful signal. This models a
   * hard crash/kill: the authoritative liveness path must detect it purely from
   * the absence of heartbeats (RFC-001 §4.5, A3), not from any native presence
   * change. Idempotent.
   */
  pauseHeartbeat(): void {
    this.loop?.stop();
    this.loop = undefined;
  }

  /** Graceful stop: stop heartbeating and drop native presence to offline. */
  async stop(): Promise<void> {
    this.pauseHeartbeat();
    try {
      await this.opts.client.setPresence(this.opts.agent.mxid, 'offline');
    } catch (err) {
      this.opts.onError?.(err);
    }
  }
}
