/**
 * Deterministic work-stealing escalation queue for dual-operator runs.
 * Algorithm only — not an agent role. Engages automatically when 2 operators exist.
 * Escalation is enqueue-and-continue — never block waiting for the peer.
 */
export class EscalationQueue {
  private q: string[] = [];
  private seen = new Set<string>();
  /** Ledger-friendly event log for enqueue / steal / resolve. */
  readonly events: { ts: string; op: 'enqueue' | 'steal' | 'resolve'; featureId: string; by?: number }[] = [];

  enqueue(featureId: string, by?: number): boolean {
    if (this.seen.has(featureId)) return false;
    this.seen.add(featureId);
    this.q.push(featureId);
    this.events.push({ ts: new Date().toISOString(), op: 'enqueue', featureId, by });
    return true;
  }

  /** Non-blocking steal: returns next queued id still in `eligible`, or null. */
  trySteal(eligible: Set<string> | string[], by?: number): string | null {
    const ok = eligible instanceof Set ? eligible : new Set(eligible);
    for (let i = 0; i < this.q.length; i++) {
      const id = this.q[i];
      if (!ok.has(id)) continue;
      this.q.splice(i, 1);
      this.events.push({ ts: new Date().toISOString(), op: 'steal', featureId: id, by });
      return id;
    }
    return null;
  }

  resolve(featureId: string, by?: number): void {
    this.q = this.q.filter(x => x !== featureId);
    this.events.push({ ts: new Date().toISOString(), op: 'resolve', featureId, by });
  }

  get size(): number {
    return this.q.length;
  }

  peek(): string[] {
    return [...this.q];
  }
}

/** Map feature id → operator index (0|1) from a partition pair. */
export function ownerMap(partA: string[], partB: string[]): Map<string, 0 | 1> {
  const m = new Map<string, 0 | 1>();
  for (const id of partA) m.set(id, 0);
  for (const id of partB) m.set(id, 1);
  return m;
}
